﻿using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Modules.Audit;
using Modules.BankSpecialAccount;
using Modules.CheckIn;
using Modules.CheckIn.Entitys;
using Modules.Common;
using Modules.Payroll.Repositories;
using Modules.Person;
using Modules.Project;
using Modules.Project.Entitys;
using Modules.Reports;
using Modules.System.Flow;
using PmSoft;
using PmSoft.ApplicationContext;
using PmSoft.Exceptions;
using System.Drawing.Printing;
using static Modules.Audit.SysAuditLogConstants;
using static Modules.Payroll.PayrollConstants;
using static Modules.Payroll.WageConstants;
using static Modules.Person.PersonConstants;
using static Modules.System.Flow.FlowConstants;

namespace Modules.Payroll
{
    [UseDI(ServiceLifetime.Scoped)]
    public class WagePayrollService
    {
        private readonly IApplicationContext applicationContext;
        private readonly WagePayrollRepository wagePayrollRepository;
        private readonly SysFlowEngineService sysFlowEngineService;
        private readonly SysAuditLogService sysAuditLogService;
        private readonly CheckInDetailService checkInDetailService;
        private readonly ProjectGroupMemberService projectGroupMemberService;
        private readonly ProjectService projectService;
        private readonly BankSpecialAccountService bankSpecialAccountService;
        private readonly WageAttachmentService wageAttachmentService;
        private readonly ILogger logger;

        public WagePayrollService(IApplicationContext applicationContext, WagePayrollRepository wagePayrollRepository, SysFlowEngineService sysFlowEngineService, SysAuditLogService sysAuditLogService, CheckInDetailService checkInDetailService, ProjectGroupMemberService projectGroupMemberService,
ProjectService projectService, ILogger<WagePayrollService> logger, BankSpecialAccountService bankSpecialAccountService, WageAttachmentService wageAttachmentService)
        {
            this.applicationContext = applicationContext;
            this.wagePayrollRepository = wagePayrollRepository;
            this.sysFlowEngineService = sysFlowEngineService;
            this.sysAuditLogService = sysAuditLogService;
            this.checkInDetailService = checkInDetailService;
            this.projectGroupMemberService = projectGroupMemberService;
            this.projectService = projectService;
            this.logger = logger;
            this.bankSpecialAccountService = bankSpecialAccountService;
            this.wageAttachmentService = wageAttachmentService;
        }

        public IPagedList<WagePayroll> GetWagePayrolls(WagePayrollQuery query, int pageIndex, int pageSize)
        {
            return wagePayrollRepository.GetWagePayrolls(query, pageIndex, pageSize);
        }

        public IEnumerable<WagePayroll> GetWagePayrolls(IEnumerable<int> ids)
        {
            return wagePayrollRepository.PopulateEntitiesByEntityIds(ids);
        }

        public IEnumerable<WagePayroll> GetWaitingWagePayrolls()
        {
            return wagePayrollRepository.GetWaitingWagePayrolls();
        }

        public WagePayroll? Get(int id)
        {
            return wagePayrollRepository.Get(id);
        }

        /// <summary>
        /// 验证方法，工资单新增、编辑、明细导入必须先调用此方法
        /// </summary>
        /// <param name="entity"></param>
        public bool Verify(WagePayroll entity)
        {
            #region 1.验证日期是否小于当月

            if (entity.Month > Convert.ToInt32(DateTime.Now.ToString("yyyyMM")))
                throw new BusinessException("工资单生成月份不能大于当前月份");

            #endregion 1.验证日期是否小于当月

            #region 2.判断当前项目当月是否有考勤记录

            if (entity.WageType != 工资单类型.非正常工资单)
            {
                var reportProjectMonthService = applicationContext.GetService<ReportProjectMonthService>();
                //IEnumerable<CheckInDetail> checkInDetails = checkInDetailService.GetCheckInDetailsByProjectIdAndMonth(entity.ProjectId, entity.Month.ToMonthNumber());
                var reportProject = reportProjectMonthService.GetReportProjectMonthByProjectIdAndMonth(entity.ProjectId, entity.Month);
                if (reportProject == null || reportProject.ActualAttendance == 0) throw new BusinessException($"当前项目在{entity.Month.ToString()}，考勤人数为0，不能申请工资单");
                IEnumerable<ProjectGroupMember> members = projectGroupMemberService.GetProjectGroupMembersByCorpIdAndProjectId(entity.CorpId, entity.ProjectId);
                var checkinWorkHoursService = applicationContext.GetService<CheckinWorkHoursService>();
                if (checkinWorkHoursService.GetTotalCheckinDays(entity.Month, entity.ProjectId, entity.CorpId) <= 0)
                    throw new BusinessException($"当前项目下的参建企业在{entity.Month}无考勤记录，不能申请工资单");
            }

            #endregion 2.判断当前项目当月是否有考勤记录

            #region 验证是否已开专户

            var account = bankSpecialAccountService.GetProjectSpecialAccountByProjectId(entity.ProjectId).Where(i => i.AccountStatus == BankSpecialAccountConstants.专户状态.正常);
            if (!account.Any()) throw new BusinessException("当前项目未申请专户，请完善专户信息后操作");

            #endregion 验证是否已开专户

            return true;
        }

        public void Create(WagePayroll entity, FlowEngine flowEngine, string flowCode)
        {
            if (Verify(entity))
            {
                entity.AuditStatus = 审核状态.无;
                entity.AuditType = 审核类型.待提交;
                entity.FlowCode = flowCode;
                entity.FlowEngineCode = flowEngine;
                wagePayrollRepository.Insert(entity);
            }
        }

        public void UpdateWagePayRoll(WagePayroll entity)
        {
            if (Verify(entity))
            {
                wagePayrollRepository.Update(entity);
            }
        }

        public void Update(WagePayroll entity)
        {
            wagePayrollRepository.Update(entity);
        }

        public void Delete(int id)
        {
            var entity = wagePayrollRepository.Get(id) ?? throw new BusinessException("工资单不存在");
            if (entity.HasCreateBatch)
                throw new BusinessException("该工资单已接入银行正式发放程序，不能删除");
            wagePayrollRepository.Delete(entity);
        }

        public void Deleted(IEnumerable<int> ids)
        {
            foreach (int id in ids)
            {
                Delete(id);
            }
        }

        /// <summary>
        /// 提交工资单审核
        /// </summary>
        /// <param name="data">工资单主数据</param>
        /// <param name="flowCode">流程编码 建议使用常量 FlowConstants</param>
        /// <exception cref="BusinessException"></exception>
        public void SubmitAudit(WagePayroll data, string flowCode, IEnumerable<string> attachments)
        {
            try
            {
                applicationContext.Transaction.Begin();//开始事务
                FlowEngine flowEngine = sysAuditLogService.SubmitAuditLog(data.Id, flowCode, "wage_payroll");
                data.FlowEngineCode = flowEngine;
                wagePayrollRepository.Update(data);

                #region 附件

                var personAttachments = wageAttachmentService.GetWageAttachmentByObjectIdAndType(data.Id, 工资附件类型.工资单凭证附件, 工资附件租户.WagePayroll).Select(a => a.Id);
                wageAttachmentService.Deleted(personAttachments);

                foreach (var item in attachments)
                {
                    WageAttachment projectAttachment = new()
                    {
                        Id = Guid.NewGuid().ToString().Replace("-", ""),
                        AttachmentId = item,
                        ObjectId = data.Id,
                        AttachmentType = 工资附件类型.工资单凭证附件,
                        CreateTime = DateTime.Now,
                        Tenant = 工资附件租户.WagePayroll
                    };
                    wageAttachmentService.Create(projectAttachment);
                }

                #endregion 附件

                applicationContext.Transaction.Complete();//完成事务
            }
            catch (Exception ex)
            {
                applicationContext.Transaction.Abort();//回滚事务
                logger.LogError(ex, $"SubmitAudit Exception code:{data.Id}");
                throw new BusinessException("写入用户数据失败");
            }
        }

        /// <summary>
        /// 工资单审核
        /// </summary>
        /// <param name="id">工资单id</param>
        /// <param name="auditIdea">审核意见</param>
        /// <param name="AuditIdeaContent">意见描述</param>
        /// <param name="personFlowEngine">流程步骤</param>
        /// <exception cref="BusinessException"></exception>
        public void Audit(int id, AuditIdea auditIdea, string? AuditIdeaContent, FlowEngine personFlowEngine)
        {
            try
            {
                WagePayroll? wagePayroll = wagePayrollRepository.Get(id);
                if (wagePayroll == null)
                    throw new BusinessException("工资单信息不存在");
                SysAuditLog sysAuditLog = new SysAuditLog()
                {
                    ObjectId = id,
                    AuditIdea = auditIdea,
                    AuditIdeaContent = AuditIdeaContent,
                    TableName = "wage_payroll"
                };
                applicationContext.Transaction.Begin();//开始事务
                bool isFirst = false;
                FlowEngine? flowEngineCode = sysAuditLogService.Audit(out isFirst, wagePayroll.FlowEngineCode, sysAuditLog, personFlowEngine);
                if (isFirst)
                    wagePayroll.AuditType = 审核类型.待提交;
                wagePayroll.FlowEngineCode = flowEngineCode.Value;
                if (flowEngineCode.Value == FlowEngine.结束)
                {
                    wagePayroll.AuditType = 审核类型.已审核;
                    wagePayroll.AuditStatus = 审核状态.成功;
                }
                wagePayrollRepository.Update(wagePayroll);
                applicationContext.Transaction.Complete();//完成事务
            }
            catch (Exception ex)
            {
                applicationContext.Transaction.Abort();//回滚事务
                logger.LogError(ex, $"CorpAudit Exception id:{id}");
                throw new BusinessException($"当前信息您暂时不能审核");
            }
        }

        /// <summary>
        /// 生成编号和流水号
        /// </summary>
        /// <param name="areaCode"></param>
        /// <returns></returns>
        public (string ProjectNum, int SerialNumber) GetNumAndSerialNumber(string projectNum, int projectId)
        {
            DateTime time = DateTime.Now;
            projectNum = projectNum.Replace(SerialNumberType.项目.GetDescription(), SerialNumberType.工资单.GetDescription());
            WagePayroll? data = wagePayrollRepository.GetWagePayrollByMaxSerialNumber(time, projectId);
            int SerialNumber = 1;
            if (data != null)
                SerialNumber = data.SerialNumber + 1;
            string ProjectNum = $"{projectNum}-{SerialNumber.ToString().PadLeft(3, '0')}";
            return (ProjectNum, SerialNumber);
        }

        /// <summary>
        /// 生成编号
        /// </summary>
        /// <param name="areaCode"></param>
        /// <returns></returns>
        public string GetCode(string projectNum, int projectId)
        {
            projectNum = projectNum.Replace(SerialNumberType.项目.GetDescription(), SerialNumberType.工资单.GetDescription());
            int serialNumber = GetWagePayrollByMaxSerialNumber(projectId);
            return $"{projectNum}-{serialNumber.ToString().PadLeft(3, '0')}";
        }

        /// <summary>
        /// 生成流水号
        /// </summary>
        /// <returns></returns>
        public int GetWagePayrollByMaxSerialNumber(int projectId)
        {
            DateTime time = DateTime.Now;
            WagePayroll? data = wagePayrollRepository.GetWagePayrollByMaxSerialNumber(time, projectId);
            if (data == null)
                return 1;
            else return data.SerialNumber + 1;
        }

        public decimal GetSumWagePayrollsSuccessfulAmount(int projectId, int corpId)
        {
            return wagePayrollRepository.GetSumWagePayrollsSuccessfulAmount(projectId, corpId);
        }

        public IEnumerable<WagePayroll> GetWagePayrollsByProjectIdsAndMonth(IEnumerable<int> projectIds, string month)
        {
            return wagePayrollRepository.GetWagePayrollsByProjectIdsAndMonth(projectIds, month);
        }

        /// <summary>
        /// 通过工资单号获取工资单
        /// </summary>
        /// <param name="code"></param>
        /// <returns></returns>
        public WagePayroll? GetWagePayrollByCode(string code)
        {
            return wagePayrollRepository.GetWagePayrollByCode(code);
        }

        /// <summary>
        /// 通过代发批次号获取工资单信息
        /// </summary>
        /// <param name="batchNo"></param>
        /// <returns></returns>
        public WagePayroll? GetWagePayrollByBatchNo(string batchNo)
        {
            return wagePayrollRepository.GetWagePayrollByBatchNo(batchNo);
        }
    }
}